<?php
namespace app\admin\controller;

use app\admin\controller\Base;
use app\common\model\CommonSetting;
use think\facade\App;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Request;
use think\facade\View;

class Tools extends Base
{
    public function index()
    {
        $path = dirname(App::getRuntimePath());
        if (Request::isPost()) {
            if (Request::post('cache_clear')) {
                Cache::clear();
            }
            if (is_dir($path)) {
                foreach (scandir($path) as $dir) {
                    if (in_array($dir, ['.', '..'])) {
                        continue;
                    }
                    if (Request::post('temp_clear')) {
                        delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'temp');
                    }
                    if (Request::post('log_clear')) {
                        delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'log');
                    }
                }
            }
            return show_success('缓存清理成功...', 'admin/tools/index');
        }
        return View::fetch();
    }

    public function database($table = '')
    {
        if ($table) {
            if (!$field = Db::query("SHOW TABLES LIKE '$table'")) {
                return show_error('数据表不存在！');
            }
            return View::assign(['table' => ['table' => $table, 'field' => Db::query("SHOW FULL COLUMNS FROM `$table`")]])->fetch();
        }
        if (Request::isPost()) {
            if (!$tables = Request::post('tables')) {
                return show_error('请选择需要处理的数据表！');
            }
            switch (Request::post('operation')) {
                case 'optimize':
                    $tables = implode('`,`', $tables);
                    Db::execute("OPTIMIZE TABLE `{$tables}`");
                    return show_success('数据表优化成功！');
                    break;

                case 'repair':
                    $tables = implode('`,`', $tables);
                    Db::execute("REPAIR TABLE `{$tables}`");
                    return show_success('数据表修复成功！');
                    break;

                default:
                    $backup_path = Request::server('DOCUMENT_ROOT') . DIRECTORY_SEPARATOR . 'databackups' . DIRECTORY_SEPARATOR . date('YmdHis') . '_' . random(12) . DIRECTORY_SEPARATOR;
                    create_dir($backup_path);
                    Cache::set('database_backups', ['tables' => implode(',', $tables), 'path' => $backup_path]);
                    return show_success("数据准备成功，正在备份数据表： {$tables[0]} ，数据备份过程中请勿关闭浏览器！", 'admin/tools/back_data', '', 0);
                    break;
            }
        }
        $table_list = Db::query('SHOW TABLE STATUS');
        return View::assign(['table_list' => $table_list])->fetch();
    }

    public function back_data($volume = 0, $start = 0, $limit = 300, $type = 'all')
    {
        $backups           = Cache::get('database_backups');
        $backups['tables'] = explode(',', $backups['tables']);
        if (empty($backups['tables'])) {
            return show_error('请选择需要备份的数据表！', 'admin/tools/database');
        }
        if (!is_dir($backups['path']) || !is_writable($backups['path'])) {
            return show_error('备份目录不存在或没有写入权限！', 'admin/tools/database');
        }

        if ($type === 'all') {
            $this->backuping($backups['tables'][0], $backups['path']);
        }
        $result = $this->backuping($backups['tables'][0], $backups['path'], 'data', $volume, $start, $limit);
        if ($result !== true) {
            $progress = round($result['progress'] / $result['count'], 4) * 100;
            return show_success('正在备份数据表： ' . $backups['tables']['0'] . ' ，已完成：<b style="color:#f00">' . $progress . '</b> %！', url('admin/tools/back_data', [
                'volume' => $result['volume'],
                'start'  => $result['start'],
                'limit'  => $result['limit'],
                'type'   => 'data',
            ]), '', 0);
        }
        unset($backups['tables'][0]);
        if (empty($backups['tables'])) {
            Cache::delete('database_backups');
            return show_success('数据库备份成功！', 'admin/tools/backup');
        }
        Cache::set('database_backups', ['tables' => implode(',', $backups['tables']), 'path' => $backups['path']]);
        return show_success('正在备份数据表： ' . $backups['tables']['1'] . ' ，数据备份过程中请勿关闭浏览器！', 'admin/tools/back_data', '', 0);
    }

    public function backup()
    {
        $path        = Request::server('DOCUMENT_ROOT') . DIRECTORY_SEPARATOR . 'databackups' . DIRECTORY_SEPARATOR;
        $backup_list = [];
        if (is_dir($path)) {
            foreach (scandir($path) as $entry) {
                if ($entry == '.' || $entry == '..') {
                    continue;
                }
                if (is_dir($path . $entry)) {
                    $rolls = $size = 0;
                    foreach (scandir($path . $entry) as $file) {
                        if ($file == '.' || $file == '..') {
                            continue;
                        }
                        $file = $path . $entry . DIRECTORY_SEPARATOR . $file;
                        if (pathinfo($file, PATHINFO_EXTENSION) == 'sql') {
                            $rolls++;
                            $size += filesize($file);
                        }
                    }
                    list($back_time, $back_name) = explode('_', $entry);
                    $backup_list[$entry]         = [
                        'back_time' => $back_time,
                        'back_name' => $back_name,
                        'rolls'     => $rolls,
                        'size'      => $size,
                    ];
                }
            }
        }
        return View::assign(['backup_list' => $backup_list])->fetch();
    }

    public function backup_delete($key = '')
    {
        $path = Request::server('DOCUMENT_ROOT') . DIRECTORY_SEPARATOR . 'databackups' . DIRECTORY_SEPARATOR;
        if (!is_dir($path . $key)) {
            return show_error('备份不存在', 'admin/tools/backup');
        }
        delete_dir($path . $key);
        return show_success('备份文件删除成功！', 'admin/tools/backup');
    }

    public function run_sql()
    {
        if (Request::isPost()) {
            if (!$sql = Request::post('sql')) {
                return show_error('SQL语句为空！');
            }
            $result = $this->_run_sql($sql);
            if ($result['error']) {
                return show_success('部分SQL执行失败，影响记录数：' . $result['success'] . '，失败数：' . $result['error']);
            }
            return show_success('所有SQL语句执行成功，影响记录数：' . $result['success']);
        }
        return View::fetch();
    }

    public function restore($key = '')
    {
        $path = Request::server('DOCUMENT_ROOT') . DIRECTORY_SEPARATOR . 'databackups' . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR;
        if (!is_dir($path)) {
            return show_error('备份不存在', 'admin/tools/backup');
        }
        $tables = $result = [];
        $size   = 0;
        foreach (scandir($path) as $file) {
            if (!in_array($file, ['.', '..']) && pathinfo($file, PATHINFO_EXTENSION) == 'sql') {
                $name = pathinfo($file, PATHINFO_FILENAME);
                if (strpos($name, '-') === false) {
                    $tables[$name][] = '';
                } else {
                    $name               = explode('-', $name);
                    $tables[$name[0]][] = $name[1];
                }
                $size += filesize($path . $file);
            }
        }
        foreach ($tables as $t => $volume) {
            $result[] = $t;
            if ($volumes = array_filter($volume)) {
                sort($volumes);
                foreach ($volumes as $v) {
                    $result[] = $t . '-' . $v;
                }
            }
        }
        Cache::set('backup_restore', $result);
        Cache::set('backup_restore_count', ['files' => count($result), 'size' => $size, 'completed' => 0]);
        return show_success("数据整理完毕，正在恢复 $result[0] 数据表，请勿关闭浏览器...", url('admin/tools/restoring', ['key' => $key]), '', 0);
    }

    public function restoring($key = '')
    {
        $path = Request::server('DOCUMENT_ROOT') . DIRECTORY_SEPARATOR . 'databackups' . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR;
        if (!is_dir($path)) {
            return show_error('备份不存在');
        }
        if ($restore = Cache::get('backup_restore')) {
            $file  = $path . $restore[0] . '.sql';
            $count = Cache::get('backup_restore_count');
            $count['completed'] += filesize($file);
            $sql = file_get_contents($file);
            $this->_run_sql($sql);
            if ($restore = array_splice($restore, 1)) {
                Cache::set('backup_restore', $restore);
                Cache::set('backup_restore_count', $count);
                $progress = round($count['completed'] / $count['size'], 4) * 100;
                return show_success('正在恢复 ' . $restore[0] . ' 数据表，已完成：<b style="color:#f00">' . $progress . '</b> %！', url('admin/tools/restoring', ['key' => $key]), '', 0);
            }
            Cache::delete('backup_restore_count');
            Cache::delete('backup_restore');
        }
        return show_success('备份恢复成功！', 'admin/tools/backup');
    }

//    public function check_new_version()
//    {
//        $upgrade_info = http_post('upgrade');
//        $now_version  = CommonSetting::where('key', '=', 'version')->value('value');
//        if ($upgrade_info['version'] > $now_version) {
//            return show_success('发现新版本：' . $upgrade_info['version'], '', ['new_version' => $upgrade_info['version']]);
//        }
//        return show_error('没有新版本');
//    }

    public function upgrade()
    {
        global $_G;

        $upgrade_info = http_post('upgrade');
        if ($upgrade_info['code'] <= 0) {
            return show_error('接口调用失败，请联系开发者');
        }
        $upgrade_info['ZipArchive']  = class_exists('ZipArchive');
        $upgrade_info['now_version'] = CommonSetting::where('key', '=', 'version')->value('value');
        return View::assign($upgrade_info)->fetch();
    }

    public function start_upgrade($upgrade_url = '', $start = false)
    {
        global $_G;

        if (!class_exists('ZipArchive') || empty($upgrade_url) || substr($upgrade_url, 0, strlen($_G['setting']['api_domain'])) != $_G['setting']['api_domain']) {
            return redirect('admin/tools/upgrade');
        }
        if (empty($start)) {
            return show_loading('更新包下载中，请勿关闭浏览器', url('admin/tools/start_upgrade', ['upgrade_url' => $upgrade_url, 'start' => 1]));
        }

        $upgrade_file = App::getRuntimePath() . DIRECTORY_SEPARATOR . 'upgrade.zip';
        $upgrade_dir  = App::getRuntimePath() . DIRECTORY_SEPARATOR . 'upgrade';
        $result       = http_post(substr($upgrade_url, strlen($_G['setting']['api_domain'])), [], [], 30, true, $upgrade_file);
        if ($result['code'] !== 1) {
            return show_error($result['msg'], 'admin/tools/upgrade');
        }

        $zip = new \ZipArchive;
        if ($zip->open($upgrade_file) !== true) {
            return show_error('升级失败，请联系开发者！', 'admin/tools/upgrade');
        }
        $zip->extractTo($upgrade_dir);
        $zip->close();
        @unlink($upgrade_file);
        $this->upgrade_file($upgrade_dir, strlen($upgrade_dir));
        delete_dir($upgrade_dir);
        return show_success('文件更新成功，正在更新数据库...', url('admin/tools/upgrade_db'));
    }

    private function upgrade_file($dir = '', $length = 0)
    {
        if (!is_dir($dir)) {
            return true;
        }

        foreach (scandir($dir) as $file) {
            if (in_array($file, ['.', '..'])) {
                continue;
            }
            $file = $dir . DIRECTORY_SEPARATOR . $file;
            if (is_file($file)) {
                $new = App::getRootPath() . DIRECTORY_SEPARATOR . substr($file, $length + 1);
                if (is_file($new)) {
                    unlink($new);
                }
                rename($file, $new);
            } else if (is_dir($file)) {
                $this->upgrade_file($file, $length);
            }
        }
    }

    public function upgrade_db()
    {
        $new_sql = <<<EOF
CREATE TABLE IF NOT EXISTS `user_card` (
    `card_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'ID',
    `uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '生成者',
    `use_uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '使用者',
    `type` varchar(50) NOT NULL DEFAULT '' COMMENT '类型',
    `vip_access` text NOT NULL COMMENT 'VIP权限',
    `download_times` text NOT NULL COMMENT '下载权限',
    `balance` decimal(7,2) NOT NULL DEFAULT '0.00' COMMENT '增加余额',
    `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发生时间',
    `create_ip` varchar(20) NOT NULL DEFAULT '' COMMENT '发生ip',
    `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
    `update_ip` varchar(20) NOT NULL DEFAULT '' COMMENT '使用ip',
    `out_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '过期时间',
    `delete_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
    `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '-1失效，0-已使用，1-可用',
    PRIMARY KEY (`card_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='充值卡';

DROP TABLE IF EXISTS `web_site_cookie`;
CREATE TABLE IF NOT EXISTS `web_site_cookie`  (
	`cookie_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT COMMENT 'cookie_id',
	`site_id` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '网站ID',
	`title` varchar(500) NOT NULL DEFAULT '' COMMENT '名称，方便自己查看',
	`content` text NOT NULL COMMENT 'cookie内容',
	`day_used` mediumint(8) unsigned NOT NULL DEFAULT 0 COMMENT '今日已使用次数',
	`day_max` mediumint(8) unsigned NOT NULL DEFAULT 0 COMMENT '该COOKIE每日可用次数',
	`all_used` mediumint(8) unsigned NOT NULL DEFAULT 0 COMMENT '总是用次数',
	`all_max` mediumint(8) unsigned NOT NULL DEFAULT 0 COMMENT '总可用次数',
	`use_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '最后使用时间',
	`available` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0-失效，1-有效',
	`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0-禁用，1-启用',
	PRIMARY KEY (`cookie_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='站点Cookie';

EOF;
        if (CommonSetting::where('key', '=', 'version')->value('value') != '20191001001') {
            $new_sql .= "ALTER TABLE `web_site` ADD `cookies_count` varchar(255) NOT NULL DEFAULT '' AFTER `demo_url`;";
        }

        if (!empty($new_sql)) {
            $this->_run_sql($new_sql);
        }
        CommonSetting::where('key', '=', 'api_domain')->update(['value' => 'http://39.100.225.5/']);
        CommonSetting::where('key', '=', 'version')->update(['value' => '20191001002']);
        $path = dirname(App::getRuntimePath());
        Cache::clear();
        if (is_dir($path)) {
            foreach (scandir($path) as $dir) {
                if (in_array($dir, ['.', '..'])) {
                    continue;
                }
                delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'admin');
                delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'cache');
                delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'index');
                delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'session');
                delete_dir($path . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'static');
            }
        }
        return show_success('程序升级成功，请更新缓存', url('admin/tools/index'));
    }

    private function _run_sql($sql = '')
    {
        if (!$sql) {
            return false;
        }
        $i = $error = 0;
        foreach ($this->_split_sql(str_replace(
            [' {prefix}', ' prefix_', ' `prefix_', ' adt_', ' `adt_'],
            [' ' . config('database.prefix'), ' ' . config('database.prefix'), ' `' . config('database.prefix'), ' ' . config('database.prefix'), ' `' . config('database.prefix')],
            $sql)) as $sql) {
            try {
                $result = Db::execute($sql);
                if ($result !== false) {
                    $i++;
                }
            } catch (\Exception $e) {
                $error++;
            }
        }
        return ['success' => $i, 'error' => $error];
    }

    private function _split_sql($sql)
    {
        $sql          = str_replace([PHP_EOL, "\r"], "\n", $sql);
        $ret          = [];
        $num          = 0;
        $queriesarray = explode(";\n", trim($sql));
        unset($sql);
        foreach ($queriesarray as $query) {
            $queries   = explode("\n", trim($query));
            $ret[$num] = '';
            foreach ($queries as $query) {
                $ret[$num] .= substr($query, 0, 1) == "#" ? null : $query;
            }
            $num++;
        }
        return ($ret);
    }

    private function backuping($table = '', $path = '', $type = 'structure', $volume = 0, $start = 0, $limit = 300)
    {
        $backups_completed = Cache::get('backups_completed') ?: [];
        $filename          = $path . $table . ($volume ? '-' . $volume : '') . '.sql';
        $dump              = '';
        if ($type === 'structure') {
            $create = Db::query("SHOW CREATE TABLE `$table`");
            $dump .= "DROP TABLE IF EXISTS `$table`;\n";
            $dump .= $create[0]['Create Table'] . ";\n\n";
            unset($backups_completed[$table]);
            Cache::set('backups_completed', $backups_completed);
            return file_put_contents($filename, $dump);
        } else if ($type === 'data') {
            $i = 1;
            foreach (Db::table($table)->limit($start, $limit)->select() as $data) {
                $i++;
                $comma = $t = '';
                foreach (Db::query('SHOW FULL COLUMNS FROM `' . $table . '`') as $field) {
                    if ((stripos($field['Type'], 'char') !== false || stripos($field['Type'], 'text') !== false) && !empty($data[$field['Field']])) {
                        $t .= $comma . '0x' . bin2hex($data[$field['Field']]);
                    } else {
                        $t .= $comma . (is_null($data[$field['Field']]) ? var_export(null, true) : "'" . $data[$field['Field']] . "'");
                    }
                    $comma = ',';
                }
                $dump .= "INSERT INTO $table VALUES ($t);\n";
            }
            file_put_contents($filename, $dump, FILE_APPEND);
            clearstatcache();
            $filesize                  = filesize($filename);
            $backups_completed[$table] = ['progress' => (empty($backups_completed[$table]['progress']) ? 0 : intval($backups_completed[$table]['progress'])) + $i - 1];
            Cache::set('backups_completed', $backups_completed);
            if ($i <= $limit) {
                return true;
            } else {
                if ($filesize + 500 >= 2048000) {
                    return ['volume' => $volume + 1, 'start' => $start + $limit, 'limit' => $limit, 'count' => Db::table($table)->count(), 'progress' => $backups_completed[$table]['progress']];
                }
                return $this->backuping($table, $path, 'data', $volume, $start + $limit, $limit);
            }
        }

    }
}
